<?php

function dumpTokens($t) {
    echo '<table>';
    foreach ($t as $tt) {
        if (is_string($tt)) {
            echo "<tr><td colspan=2>$tt</td></tr>";
        } else {
            echo '<tr><td>' . token_name($tt[0]) . '</td><td>' . $tt[1] . '</td></tr>';
        }
    }
    echo '</table>';
}

function processFile($name) {
    $out = '';
    $tokens = token_get_all(file_get_contents($name));
    // dumpTokens($tokens);

    //converting "namespace foo; ..." to "namespace foo { ... }"
    $ns_began = false;
    $ns_to_end = false;

    $was_namespace = false;
    $dependencies = array();
    $resolves = array();
    $php_open_state = true;
    for ($i = 0; $i < count($tokens); ++$i) {
        $token = $tokens[$i];
        if ($ns_began) {
            if ($token == '{')
                $ns_began = false;
            elseif ($token == ';') {
                $token = '{';
                $ns_began = false;
                $ns_to_end = true;
            }
        }

        if (is_string($token)) {
            $out .= $token;
        } else {
            // token array
            list($id, $text) = $token;

            switch ($id) {
                //remove comments
                case T_COMMENT:
                    $text = '';
                    break;
                case T_DOC_COMMENT: // and this
                    // no action on comments
                    continue;
                case T_CLASS:
                case T_INTERFACE:
                    $className = empty($nsName) ? '' : $nsName.'\\'.$tokens[$i+2][1];
                    $resolves[] = $className;
                    $refl = new ReflectionClass($className);
                    $dependencies = array_merge($dependencies, $refl->getInterfaceNames());
                    if ($refl->getParentClass()) $dependencies[] = $refl->getParentClass()->getName();
                    
                    break;
                //compress whitespaces
                case T_WHITESPACE:
                    if (strpos($text, "\n") !== FALSE) {
                        $text = "\n";
                    } else {
                        $text = ' ';
                    }
                    break;
                //namespace syntax conversion
                case T_NAMESPACE:
                    $ns_began = true;
                    $was_namespace = true;
                    $j = $i+2;
                    $nsName = '';
                    while ($tokens[$j][0] == T_STRING || $tokens[$j][0] == T_NS_SEPARATOR)
                    {
                        $nsName .= $tokens[$j++][1];
                    }

                    break;
                case T_OPEN_TAG:
                    if ($php_open_state)
                    {
                        $text = '';
                    }
                    $php_open_state = true;
                    
                    break;
                case T_CLOSE_TAG:
                    $php_open_state = false;
                    break;
            }
            // anything else -> output "as is"
            $out .= $text;
        }
    }
    if (!$php_open_state) {
        $out .= '<?php';

    }

    if ($ns_to_end)
        $out .= '}';
    if (!$was_namespace) {
        $out = "namespace{\n$out}";
    }

    return array('content' => $out, 'resolves' => $resolves, 'dependencies' => $dependencies, 'origin'=>$name);
}

function processDirectory($path) {
    $out = array();
    $dir = new DirectoryIterator($path);
    foreach ($dir as $item) {
        if (strtolower($item->getFilename()) == 'table.php')
        {
            $a = 0;
        }
        if ($item->isDir() && !$item->isDot()) {
            $out = array_merge($out, processDirectory($path . DIRECTORY_SEPARATOR . $item->getFilename()));
        } elseif ($item->isFile() && strtolower($item->getExtension()) == 'php') {
            if (isset($_GET['verbose'])) echo "Dir: $path ; found file: {$item->getFilename()}\n";
            $out[] = processFile($path . DIRECTORY_SEPARATOR . $item->getFilename());
        }
    }
    return $out;
}
header('content-type:text/plain');
require 'loader.php';

//process individual files
$out = processDirectory(isset($_GET['path']) ? $_GET['path'] : '.');
//get list of all found classes and interfaces
$foundClasses = array();
foreach ($out as $file)
{
    $foundClasses = array_merge($foundClasses, $file['resolves']);
}

//cleanup dependencies directed out of the minified scope
foreach ($out as &$file)
{
    foreach ($file['dependencies'] as $key=>$dependency)
    {
        if (!in_array($dependency, $foundClasses))
        {
            unset($file['dependencies'][$key]);
        }
    }
}
unset($file);

//linearize dependency graph and build output
$output = '<?php ';
$resolved = array();
if (isset($_GET['verbose']))
{
    foreach ($out as $file)
    {
        echo "{$file['origin']}: #contains ".implode(', ', $file['resolves']).' #depends on '.implode(', ', $file['dependencies'])."\n";
    }
}
while (count($out) > 0)
{
    $cycled = TRUE;
    $del = array();
    foreach ($out as $key=>$file)
    {
        if (!array_diff($file['dependencies'], $resolved, $file['resolves']))
        {
            $cycled = FALSE;
            if (isset($_GET['verbose']))
            {
                $output .= "/*F: {$file['origin']}*/";
            }
            $output .= $file['content'];
            $resolved = array_merge($resolved, $file['resolves']);
            $del[] = $key;
        }
    }
    foreach ($del as $key)
    {
        unset($out[$key]);
    }
    if ($cycled)
    {
        $e = array();
        foreach ($out as $file)
        {
            $e = array_merge($e, $file['resolves']);
        }
        throw new Exception("Cannot resolve dependencies for classes: ".implode(',', $e));
    }
}

file_put_contents('min.php', $output);